
  /***********************************************************************************************

    Object:     jsl.DataSource.CNet
    Implements: jsl.DataSource.Connector

    Note:
      request:   f(data)           ->  jsl.Url  -> send
                                       obj      -> no send

      response:  f(data, response) ->  return obj
      data                         ->  {url, input, output}

  ************************************************************************************************/

  //-------------------------------------------------------------------------------
  jsl.DataSource.CNet = {

    //private data
    //-------------------------------------------------------------------------------
    __name: "CNet",

    __ds:   null,
    __tid:  null,
    __md:   null,
    __ps:   null,
    __hds:  { any: 1, json: 1, xml: 1, blob: 1, d: 1 }, //high data

    //-------------------------------------------------------------------------------
    type: function() {
      return "net"
    },
    /*
      custom options: request, response, params
    */
    //-------------------------------------------------------------------------------
    define: function(ds, options) {
      try {
        //!
        if(ds.request   != null)  jsl.validate(ds.request,  "f", "options.request")
        if(ds.response  != null)  jsl.validate(ds.response, "f", "options.response")
        if(ds.params    != null)  jsl.validate(ds.params,   "o", "options.params")//!

        jsl.require("data")
        try       { jsl.Url(ds.location) + ""                                 }
        catch(e)  { throw jsl.ParamError("BadValue:options.location:URL", e)  }
      }
      catch(e) {
        jsl.throwError(e, this, arguments)
      }
    },
    /*
      options: params
    */
    //-------------------------------------------------------------------------------
    connect: function(ds, tid, options) {
      try {
      	var ps, cn, f, obj, dset

        //!
        if(options.hasOwnProperty("option"))            throw jsl.ParamError("BadType:options:object")
        if(options.params != null)                      jsl.validate(options.params, "o", "options.params")
        if(options.metadata && !options.metadata.reals) throw jsl.ParamError("e.dst.net.BadMetadata2:options.metadata")//!

        this.__ds   = ds
        this.__tid  = tid
        this.__md   = options.metadata

        ps = this.__ps = {}
        if(ds.params)       jsl.Object.augment(ps, ds.params)
        if(options.params)  jsl.Object.augment(ps, options.params)

        cn  = this
        f   = function(obj) {
        	var rs, nr, f

          rs = obj.rows
          nr = rs && rs.length

          if(!cn.__md)
            cn.__md = cn.__getmd(obj)

          //!
          if(rs == null)              throw jsl.DataError("e.dst.net.BadRows:NULL")
          if(!(rs instanceof Array))  throw jsl.DataError("e.dst.net.BadRows:" + typeof rs)

          for(var a = 0, l = rs.length; a < l; a++)
            if(typeof rs[a] !== "object") throw jsl.DataError("e.dst.net.BadRowType", [ a, typeof rs[a] ])//!

          f     = options.onReady
          dset  = new jsl.DataSource.DataSet(cn.__md, { readonly: options.readonly })
          dset.__ds = rs

          f && f(dset)
        }

        obj = {}
        if(tid) obj.table = tid

        //1-async
        if(options.onReady) {
          this.__cmd("connect", this.__ds, obj, f)
        	dset = null
        }
        //2-sync
        else {
          obj = this.__cmd("connect", this.__ds, obj)
          f(obj)
        }

        return dset
      }
      catch(e) {
        jsl.throwError(e, this, arguments)
      }
    },
    //-------------------------------------------------------------------------------
    execute: function(ds, command, onReady) {
      try {
        var v, cmd

        //!
        jsl.validate(command, "t(o,s+)", "command")//!

        v   = null
        cmd = jsl.isString(command) ? { data: command } : command

        if(onReady)     this.__cmd("execute", ds, cmd, onReady)
        else        v = this.__cmd("execute", ds, cmd).result

        return v
      }
      catch(e) {
        jsl.throwError(e, this, arguments)
      }
    },
    //-------------------------------------------------------------------------------
    commit: function(rows, onReady) {
    	try {
    		var rs, ors, out, a, l, b, md, nm, r, hds, Ds

    		md  = this.__md
    		nm  = md.length

    		Ds  = jsl.DataSource
    		hds = this.__hds

        out = {}
        out.added = ors = []
    		rs = rows.added
    		for(a = 0, l = rs.length; a < l; a++) {

          ors[a] = r = []
    		  for(b = 0; b < nm; b++)
    		    r[b] = hds[md[b].type] ? Ds.__toNative(rs[a][md[b].id], md[b].type) : rs[a][md[b].id]
    		}

        out.removed = ors = []
        rs = rows.removed
        for(a = 0, l = rs.length; a < l; a++) {

          ors[a] = r = []
          for(b = 0; b < nm; b++)
            r[b] = hds[md[b].type] ? Ds.__toNative(rs[a][md[b].id], md[b].type) : rs[a][md[b].id]
        }

        out.old = ors = []
        rs = rows.old
        for(a = 0, l = rs.length; a < l; a++) {

          ors[a] = r = []
          for(b = 0; b < nm; b++)
            r[b] = hds[md[b].type] ? Ds.__toNative(rs[a][md[b].id], md[b].type) : rs[a][md[b].id]
        }

        out.updated = ors = []
        rs = rows.updated
        for(a = 0, l = rs.length; a < l; a++) {

          ors[a] = r = []
          for(b = 0; b < nm; b++)
            r[b] = hds[md[b].type] ? Ds.__toNative(rs[a][md[b].id], md[b].type) : rs[a][md[b].id]
        }

        this.__cmd("commit", this.__ds, { table: this.__tid, rows: out }, onReady)
      }
      catch(e) {
        jsl.throwError(e, this, arguments)
      }
    },
    //-------------------------------------------------------------------------------
    refresh: function(onReady) {
      try {
      	var cn, f, rs, obj

        cn  = this
        f   = function(obj) {
          var rs, nr, f

          rs = obj.rows
          nr = rs && rs.length

          //!
          if(rs == null)              throw jsl.DataError("e.dst.net.BadRows:NULL")
          if(!(rs instanceof Array))  throw jsl.DataError("e.dst.net.BadRows:" + typeof rs)//!

          f = onReady
          f && f(obj.rows)
        }

        //1-async
        if(onReady) {
          this.__cmd("connect", this.__ds, { table: this.__tid }, f)
          rs = null
        }
        //2-sync
        else {
          obj = this.__cmd("connect", this.__ds, { table: this.__tid })
          f(obj)
          rs = obj.rows
        }

        return rs
      }
      catch(e) {
        jsl.throwError(e, this, arguments)
      }
    },
    //private
    //-------------------------------------------------------------------------------
    __type: function(v) {
      var t, D

      D = jsl.Data

      if(typeof v === "number")       t = "n"
      else if(typeof v === "boolean") t = "b"
      else if(typeof v === "string")  t = "s"
      else if(v instanceof Date)      t = "d"
      else if(D.Blob.isClassOf(v))    t = "blob"
      else if(D.XNode.isClassOf(v))   t = "xml"
      else                            t = "any"

      return t
    },
    //-------------------------------------------------------------------------------
    __getmd: function(obj) {
      var md, rs, nr, a, id

      rs = obj.rows
      nr = rs && rs.length

      //1-sent from the back-end
      if(obj.metadata)
       md = obj.metadata

      //2-detection
      else if(nr) {//!
        if(typeof rs[0] !== "object")   throw jsl.DataError("e.dst.net.BadRowType", [ 0, typeof rs[0] ])//!

        md = []

        if(rs[0] instanceof Array)
          for(a = 0; a < rs[0].length; a++)
            md[a] = {
              id:   "col" + a,
              type: this.__type(rs[0][a])
            }

        else {

          a = 0
          for(id in rs[0])
            md[a++] = {
              id:   id,
              type: this.__type(rs[0][id])
            }

          md.sort(function(e1, e2){ return e1.id <= e2.id ? -1 : 1 })
        }
      }
      //!3-error
      else
        throw jsl.ParamError("e.dst.net.MetadataNotFound")//!

      try       { md = jsl.DataSource.__md(md) }
      catch(e)  { throw jsl.DataError("e.dst.net.BadMetadata", e)  }

      return md
    },
    //-------------------------------------------------------------------------------
    __cmd: function(id, ds, data, onReady) {
      var f, v, obj, out, dt, cn

      //0-preparation
      //-------------------------------------------------------------
      dt = this.__ps || ds.params ? jsl.Object.clone(this.__ps || ds.params) : {}
      dt = jsl.Object.augment(dt, data)

      dt.command    = id
      dt.datasource = ds.id

      dt = { url: jsl.Url(ds.location), input: dt, output: null }
      f  = ds.request

      if(f) {
        try       { v = f(dt) }
        catch(e)  { throw jsl.ParamError("BadScript:options.request", e) }//!
        if(!jsl.isObject(v))  throw jsl.ParamError("BadReturnValue:options.request:object")//!
      }
      else
        v = dt.url

      if(v instanceof jsl.Url) {

        if(dt.output != null) {//!
          if(!jsl.isObject(dt.output)) throw jsl.MisuseError("BadType:request(data.output):object")//!
          out = dt.output
        }
        else {

        	out = {}
          for(var p in dt.input)
            if(dt.input.hasOwnProperty(p))
              if(dt.input[p] && typeof dt.input[p] === "object")
                out[p] = jsl.Data.toJson(dt.input[p], jsl.Data.__JSN_OPTS)
              else
                out[p] = dt.input[p]

        }

        f = ds.response

        //1-async rq
        //-------------------------------------------------------------
        if(onReady) {

        	cn = this
          jsl.request(v, { content: f ? "text" : "json", method: "post", data: out, onReceive: function(e) {
        		var obj, v
        		v = cn.__cm_f(e.data, dt)
        		onReady(v)
        	}})
          v = null
        }
        //2-sync rq
        //-------------------------------------------------------------
        else {
          try       { obj = jsl.request(v, { content: f ? "text" : "json", method: "post", data: out }) }
          catch(e)  { obj = { error: e } }

          v = this.__cmd_f(obj, dt, f)
        }
      }

      return v
    },
    //-------------------------------------------------------------------------------
    __cmd_f: function(obj, dt, f) {
    	var v

      if(jsl.isObject(obj) && obj.error) throw jsl.DataError(obj.error)

      if(f) {
        try       { v = f(dt, obj) }
        catch(e)  { throw jsl.ParamError("BadScript:options.response", e) }//!
        if(!jsl.isObject(v))  throw jsl.ParamError("BadReturnValue:options.response:object")//!
      }
      else
        v = obj

      return v
    }
  }

  //-------------------------------------------------------------------------------
  jsl.DataSource.type("net", jsl.DataSource.CNet)
